{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "from aco import ACO\n",
    "import sys\n",
    "import numpy as np\n",
    "from scipy.spatial import distance_matrix\n",
    "import logging\n",
    "import inspect\n",
    "\n",
    "\n",
    "N_ANTS = 30\n",
    "N_ITERATIONS = [1, 10, 30, 50, 100, 150, 200]\n",
    "CAPACITY = 50\n",
    "\n",
    "def heuristics_reevo(distance_matrix: np.ndarray, coordinates: np.ndarray, demands: np.ndarray, capacity: int) -> np.ndarray:\n",
    "    num_nodes = distance_matrix.shape[0]\n",
    "    \n",
    "    # Calculate the inverse of the distance matrix\n",
    "    inverse_distance_matrix = np.divide(1, distance_matrix, where=(distance_matrix != 0))\n",
    "    \n",
    "    # Calculate total demand and average demand\n",
    "    total_demand = np.sum(demands)\n",
    "    average_demand = total_demand / num_nodes\n",
    "    \n",
    "    # Calculate the distance from each node to the starting depot\n",
    "    depot_distances = distance_matrix[:, 0]\n",
    "    \n",
    "    # Calculate the remaining capacity of the vehicle for each node\n",
    "    remaining_capacity = capacity - demands\n",
    "    \n",
    "    # Initialize the heuristic matrix\n",
    "    heuristic_matrix = np.zeros_like(distance_matrix)\n",
    "    \n",
    "    # Calculate the demand factor and distance factor\n",
    "    demand_factor = demands / total_demand\n",
    "    normalized_distance = distance_matrix / np.max(distance_matrix)\n",
    "    distance_factor = depot_distances / (normalized_distance + np.finfo(float).eps)\n",
    "    \n",
    "    # Iterate over each node\n",
    "    for i in range(num_nodes):\n",
    "        \n",
    "        # Calculate the heuristic value based on distance and capacity constraints\n",
    "        heuristic_values = inverse_distance_matrix[i] * (1 / (normalized_distance[i] ** 2))\n",
    "        \n",
    "        # Adjust the heuristic values based on the remaining capacity\n",
    "        heuristic_values = np.where(remaining_capacity >= demands[i], heuristic_values, 0)\n",
    "        \n",
    "        # Adjust the heuristic values based on the demand factor\n",
    "        heuristic_values *= demand_factor[i] / average_demand\n",
    "        \n",
    "        # Adjust the heuristic values based on the distance factor\n",
    "        heuristic_values *= distance_factor[i]\n",
    "        heuristic_values[0] = 0  # Exclude the depot node\n",
    "        \n",
    "        # Adjust the heuristic values based on the capacity utilization\n",
    "        utilization_factor = np.where(remaining_capacity >= demands[i], capacity - demands[i], 0)\n",
    "        heuristic_values *= utilization_factor\n",
    "        \n",
    "        # Set the heuristic values for the current node in the heuristic matrix\n",
    "        heuristic_matrix[i] = heuristic_values\n",
    "    \n",
    "    return heuristic_matrix\n",
    "\n",
    "def solve(node_pos, demand):\n",
    "    dist_mat = distance_matrix(node_pos, node_pos)\n",
    "    dist_mat[np.diag_indices_from(dist_mat)] = 1 # set diagonal to a large number\n",
    "    heu = heuristics_reevo(dist_mat, node_pos, demand, CAPACITY) + 1e-9\n",
    "    heu[heu < 1e-9] = 1e-9\n",
    "    aco = ACO(dist_mat, demand, heu, CAPACITY, n_ants=N_ANTS)\n",
    "    \n",
    "    results = []\n",
    "    for i in range(len(N_ITERATIONS)):\n",
    "        if i == 0:\n",
    "            obj = aco.run(N_ITERATIONS[i])\n",
    "        else:\n",
    "            obj = aco.run(N_ITERATIONS[i] - N_ITERATIONS[i-1])\n",
    "        # print(\"Iteration: {}, Objective: {}\".format(N_ITERATIONS[i], obj))\n",
    "        results.append(obj.item())\n",
    "    return results\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[*] Running ...\n",
      "[*] Average for 20, 1 iterations: 5.359106900150456\n",
      "[*] Average for 20, 10 iterations: 4.856290960054639\n",
      "[*] Average for 20, 30 iterations: 4.780728418281577\n",
      "[*] Average for 20, 50 iterations: 4.763402845346687\n",
      "[*] Average for 20, 100 iterations: 4.739372008116089\n",
      "[*] Average for 20, 150 iterations: 4.729715278567657\n",
      "[*] Average for 20, 200 iterations: 4.725332738131605\n",
      "\n",
      "[*] Average for 50, 1 iterations: 10.742579572004573\n",
      "[*] Average for 50, 10 iterations: 9.711407913619976\n",
      "[*] Average for 50, 30 iterations: 9.265205121184207\n",
      "[*] Average for 50, 50 iterations: 9.11182004021968\n",
      "[*] Average for 50, 100 iterations: 8.988891687862166\n",
      "[*] Average for 50, 150 iterations: 8.926836021977824\n",
      "[*] Average for 50, 200 iterations: 8.906123444482922\n",
      "\n",
      "[*] Average for 100, 1 iterations: 18.326012464706558\n",
      "[*] Average for 100, 10 iterations: 16.93568059110451\n",
      "[*] Average for 100, 30 iterations: 16.08912256400286\n",
      "[*] Average for 100, 50 iterations: 15.766953335921835\n",
      "[*] Average for 100, 100 iterations: 15.50433433271301\n",
      "[*] Average for 100, 150 iterations: 15.417620573350725\n",
      "[*] Average for 100, 200 iterations: 15.37267454681707\n",
      "\n"
     ]
    }
   ],
   "source": [
    "print(\"[*] Running ...\")\n",
    "    \n",
    "\n",
    "for problem_size in [20, 50, 100]:\n",
    "    dataset_path = f\"dataset/val{problem_size}_dataset.npy\"\n",
    "    dataset = np.load(dataset_path)\n",
    "    demands, node_positions = dataset[:, :, 0], dataset[:, :, 1:]\n",
    "    \n",
    "    n_instances = node_positions.shape[0]\n",
    "    logging.info(f\"[*] Evaluating {dataset_path}\")\n",
    "    \n",
    "    objs = []\n",
    "    for i, (node_pos, demand) in enumerate(zip(node_positions, demands)):\n",
    "        obj = solve(node_pos, demand)\n",
    "        objs.append(obj)\n",
    "    \n",
    "    # Average objective value for all instances\n",
    "    mean_obj = np.mean(objs, axis=0)\n",
    "    for i, obj in enumerate(mean_obj):\n",
    "        print(f\"[*] Average for {problem_size}, {N_ITERATIONS[i]} iterations: {obj}\")\n",
    "    print()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
